{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению.\n",
    "\n",
    "### <center> Автор материала: Тышов Никита @nikt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Keystroke biometrics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B> Ценность исследования. </B> \n",
    "\n",
    "Очередная задача биометрической идентификации человека. Задача основана на идее о том, что биометрические признаки: отпечаток пальца, лицо, рисунок сетчатки, рисунок радужки и т.д. трудно подделать так как они уникальны и формируются под огромным количеством несвязанных факторов и не поддаются корректировке. Однако, как показало время, технологии не стоят на месте, и параллельно росту качества систем распознавания растет и качество систем взлома. Так биометрические признаки являются устойчивыми довольно длительное время, поэтому их можно попробовать скопировать и подложить, как например с отпечатками пальцев и лицами. Поэтому идут поиски систем на основе поведенческой биометрии. Поведение так же считается уникальным, при этом его довольно трудно скопировать.\n",
    "\n",
    "Keystroke biometrics рассматривает различные ситуации: верификация человека на основе свободно набираемого текста, или на основе определенно набираемого текста, на основе текста который просит система и т.д. вариаций может быть множество и каждая хороша в своих условиях. Здесь будет рассмотренна распространненая ситуация, когда ваш логин  и пароль был скомпрометирован. То есть, злоумышленник без лишних хлопот может войти в нужный ему аккаунт. Сейчас в основном такая ситуация разрешается при помощи двухфакторной верификации, но это не отменяет попыток найти альтернативы. В данной ситуации двухфакторной верификации нет и необходимо верифицировать пользователя по поведению в наборе пароля.\n",
    "\n",
    "Также определю два общих для биометрических систем термина: верификация - это когда мы знаем кто стучится, например на основе логина, и пытаемся сказать тот человек или не тот, обычно это задача one-vs-all, то есть бинарная. И идентификация - это когда мы не знаем о человеке ничего кроме его биометрических характеристик и пытаемся найти его в базе или сказать, что такого человека мы вообще не знаем, такая задача почти является классификацией, но классов здесь обычно неограниченное число."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B>Процесс сбора данных.</B> \n",
    "http://www.cs.cmu.edu/~keystroke/DSL-StrongPasswordData.csv\n",
    "\n",
    "Датасет был собран в <I>Carnegie Mellon University</I> для испытания различных моделей распознавания. Датасет представляет собой тайминг нажатий клавишь на клавиатуре при вводе пароля .tie5Roanl. Пароль был сгенериван случайно и по правилам составляния паролей является сложным так как содержит и знаки препинания и цифры, большие и маленькие буквы. Также пароль имеет приемлемую длину в 10 символов. Основная мотивация при составлении датасета была следующая: предположим что злоумышленнык знает пароль, сможем ли мы идентифицировать его только по стилю набора. \n",
    "    \n",
    "Были вызваны случайные 51 человек, каждый из которых в течении 8 сессий( каждая сессия проходила не чаще чем раз в день) набирал этот пароль 50 раз. Ошибки ввода не принимались, то есть при совершении ошибки необходимо было перенабирать пароль заново. Данные собраны очень грамотно, распределенны во времени и позволяют привыкнуть к паролю, что имитирует ситуацию когда пароль вводится на автомате \"мышечной памятью\". Следовательно так как пароль один и все испытуемые видели его впервые и набирали в одних и тех же условиях, единственным их отличием должно стать именно поведение."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B>Целевая переменная.</B> \n",
    "\n",
    "Целевой переменной является соответствие личности входящего, личности шаблона или класса с которым его сравнивают."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<B>Описание признаков.</B> \n",
    "\n",
    "Как уже было сказано, датасет содержит 51 человека по 8 сессий по 50 наборов, в сумме 20400 записей, и 34 признака.\n",
    "* subject - id объекта(человека)\n",
    "* sessionIndex - номер сессии\n",
    "* rep - номер попытки внутри сессии\n",
    "\n",
    "Далее идут временный интервалы. Определим:\n",
    "* key-down : kd - момент нажатия клавиши\n",
    "* key-up : ku - момент отпускания клавиши\n",
    "* key - определенная клавиша у нас это . t i e ...\n",
    "\n",
    "Признаки\n",
    "* H.key - время удержания key т.е ku.key-kd.key\n",
    "* DD.key1.key2 - время между kd следующих друг за другом клавишь т.е kd.key2 - kd.key1\n",
    "* UD.key1.key2 - время между ku первой клавиши и kd следующей т.е kd.key2 - ku.key1\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Первичный анализ данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib import pyplot\n",
    "import seaborn\n",
    "import numpy as np\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('../../../DSL-StrongPasswordData.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на данные."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данных нет пропусков. Также можно заметить дополнительные нажатия shift и enter: H.Shift.r, H.Return.\n",
    "\n",
    "Первые три признака subject, sessionIndex, rep как таковыми признаками не являются, это идентификаторы позволяющие определять принадлежность данных. Далее мы их уберем.\n",
    "\n",
    "Посмотрим на статистику в данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для лучшего рассмотрения разобьем описание по типам признаков: отдельно для H, DD и UD"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[[x for x in data.columns if 'H' in x]].describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[[x for x in data.columns if 'DD' in x]].describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[[x for x in data.columns if 'UD' in x]].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оказывается UD может быть отрицательным, это говорит о том, что человек нажимает следующую клавишу до того как отпустил предыдущую.\n",
    "\n",
    "Выделим идентификаторы в отдельный датафрейм, чтобы они нам не мешали."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "indx_col = ['subject','sessionIndex','rep']\n",
    "indx = data[indx_col]\n",
    "label = [ int(x[1:]) for x in data['subject']]\n",
    "features = data.drop(columns=indx_col)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Первичный визуальный анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Исследуем корреляцию Пирсона числовых признаков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a4_dims = (11.7, 8.27)\n",
    "df = features.corr()\n",
    "fig, ax = pyplot.subplots(figsize=a4_dims)\n",
    "seaborn.heatmap(ax=ax, data=df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По матрице корреляции выявлена сильная зависимость между UD и DD признаками на каждом символе. Посмотрев после этого на данные более пристально, понял, что DD = H + UD. Вспомним, что \n",
    "* DD = kd.key2 - kd.key1 \n",
    "* UD = kd.key2 - ku.key1 \n",
    "* H = ku.key1 - kd.key1\n",
    "Удалим DD как избыточный признак, и еще раз посмотрим на матрицу корреляции."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "features_H_UD = features.drop(columns=[x for x in features.columns if 'DD' in x])\n",
    "df = features_H_UD.corr()\n",
    "fig, ax = pyplot.subplots(figsize=a4_dims)\n",
    "seaborn.heatmap(ax=ax, data=df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уже лучше. Больше сильных корреляций не наблюдается. Попробуем применить PCA и разложить вектора на плоскости, вдруг все испытуемые хорошо линейно разделимы и лежат обособленно в кластерах."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.decomposition import PCA\n",
    "pca = PCA(n_components=2)\n",
    "features_pca = pca.fit_transform(features_H_UD)\n",
    "\n",
    "plt.figure(figsize=(12,10))\n",
    "plt.scatter(features_pca[:, 0], features_pca[:, 1], c=label, \n",
    "            edgecolor='none', alpha=0.7, s=40,\n",
    "            cmap=plt.cm.get_cmap('nipy_spectral', 51))\n",
    "plt.colorbar()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так просто данные разделить не получилось. Посмотрим какую дисперсию описывают признаки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pca = PCA().fit(features_H_UD)\n",
    "plt.figure(figsize=(10,7))\n",
    "plt.plot(np.cumsum(pca.explained_variance_ratio_), color='k', lw=2)\n",
    "plt.xlabel('Number of components')\n",
    "plt.ylabel('Total explained variance')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что двух измерений очень мало. С данными необходимо работать как с многомерными векторами.\n",
    "\n",
    "Визуальный анализ выявил корреляции которые были пропущены при знакомстве с датасетом. Корреляции устранены. Опробован PCA в качестве оценки решения в лоб. Визуально задача в двухмерном пространсве не решается, потому что двух и трех измерений очень мало. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Инсайты, найденные зависимости"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В целом данные не содержат инстайтов, пробелов и каких либо упущений которые бы сделали данные невалидными. В процессе визуального и первичного анализа выявлены и устранены зависимые переменные и обнаружена не тривиальная оссобенность признаков UD, говорящая о том, что при печати испытуемые могут нажимать следующую клавишу перед тем как отпустили предыдущую."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Выбор метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве основной метрики для оценки качества модели выбран критерий ROC-AUC. Такая метрика хорошо работает с несбалансированными классами. Разбалансированность обычное дело в задачах верификации. В  биометрии ROC кривая является основным показателем качества, так же по нему подбирают пороги при которых насколько возможно минимизируется False positive rate(FPR - ложный пропуск, когда мы пропускаем не того), обычно при уменьшении FAR уменьшается и True positive rate(TPR - пропуск своего, когда мы пропускаем того кого надо) ROC кривая позволяет найти компромисс."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Выбор модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Основной задачей является задача верификации, когда нам необходимо определить тот человек или не тот, с учетом того, что мы знаем, кто должен верифицироваться. То есть, это бинарная классификация. Так как у нас 51 человек, получается необходимо будет обучить 51 бинарный классификатор. \n",
    "\n",
    "Попробуем решить задачу несколькими методами:\n",
    "    - метрический, решение принимается на основании расстояния до среднего вектора в классе, будет рассмотрен как линейный классификатор\n",
    "    - RandomForestClassifier , будет рассмотрен как нелинейный классификатор\n",
    "    \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Предобработка данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Данные являются зависимыми во времени, так как пароль вводился в восьми последовательных сеансах. Учтем, что пользователь во время ввода привыкал к паролю и поведение в процессе менялось. Для обучения будем брать первые 3 сеанса верефицируемого пользователя, то есть первые 150 вводов, это не очень правдоподобно(заставить пользователя при регистрации ввести пароль 150 раз не очень гуманно), но и не фантастика. А остальные 250 для теста. В качестве второго класса(атакующих) возьмем по 5 первых последовательных попыток для обучения. А для теста возьмем еще по 5 начиная с 6 с шагом 79, так у нас будет имитация того, что злоумышленник тренируется в набивании пароля.\n",
    "\n",
    "Так как потребуется обучать много классификаторов, по одному на каждого испытуемого, пересоберем данные в удобный словарь, чтобы было легко их доставать в зависимости от целевого испытуемого. Признаки упакуем в вектор. Так как данные изначально правильно отсортированы это можно сделать последовательно. Дополнительно напишем функцию по разделению данных на классы в зависимости от того какой человек верифицируется."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "print(len(label), features_H_UD.values.shape)\n",
    "\n",
    "data_dict = defaultdict(list)\n",
    "\n",
    "for ind,l in enumerate(label):\n",
    "    data_dict[l].append(features_H_UD.values[ind])\n",
    "    \n",
    "data_len = len(data_dict)\n",
    "print(data_len)\n",
    "\n",
    "\n",
    "def prepare_data(data_dict, index):\n",
    "    train_x = []\n",
    "    test_x = []\n",
    "    train_y = []\n",
    "    test_y = []\n",
    "    \n",
    "    for ind, i in enumerate(data_dict):\n",
    "        if ind==index:\n",
    "            train_x.extend(data_dict[i][:150])\n",
    "            train_y.extend([1]*150)\n",
    "            test_x.extend(data_dict[i][150:])\n",
    "            test_y.extend([1]*250)\n",
    "        else:\n",
    "            train_x.extend(data_dict[i][:5])\n",
    "            train_y.extend([0]*5)\n",
    "            test_x.extend(data_dict[i][5::79])\n",
    "            test_y.extend([0]*5)\n",
    "    return train_x, train_y, test_x, test_y\n",
    "\n",
    "trte = prepare_data(data_dict,0)\n",
    "for i in trte:\n",
    "    print(np.array(i).shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так как настраивать параметры нужно только RandomForest, то на данном этапе работать будем с ним. Параметры будем подбирать на одном испытуемом при помощи кросс-валидации. Переберем большое количество параметров, но параметры сложности и количества деревьев будем искать возле минимальных значений, так как данных очень мало и можно легко переобучиться, возьмем 5 фолдов чтобы сократить время поиска. Не забудем, что метрика ROC-AUC."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection  import StratifiedKFold\n",
    "from sklearn.model_selection  import GridSearchCV\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "RANDOM_STATE = 42\n",
    "parameters = {'n_estimators':[50,100,200], 'max_features': [4, 7, 10], \n",
    "              'min_samples_leaf': [3, 5, 7], 'max_depth': [3,5,10]}\n",
    "\n",
    "np.random.seed(RANDOM_STATE)\n",
    "train_x, train_y, test_x, test_y = prepare_data(data_dict,index=np.random.randint(len(data_dict)))\n",
    "\n",
    "\n",
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)\n",
    "\n",
    "rfc = RandomForestClassifier(random_state=RANDOM_STATE,n_jobs=-1)\n",
    "gcv = GridSearchCV(rfc, parameters, n_jobs=-1, cv=skf, verbose=1,scoring='roc_auc')\n",
    "gcv.fit(train_x, train_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gcv.best_params_, gcv.best_score_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Кросс валидация выбрала следующие параметры\n",
    " - max_depth 10\n",
    " - max_features 4\n",
    " - min_samples_leaf 3\n",
    " - n_estimators 200\n",
    "\n",
    "значение ROC AUC очень высокое, возможно при тестировании обнаружится переобучение."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import roc_auc_score\n",
    "y_pred = gcv.best_estimator_.predict_proba(test_x)\n",
    "roc_auc_score(test_y,y_pred[:,1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тестовая выборка показала небольшое проседание, но не критичное, следовательно найденные параметры можно считать валидными."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. Создание новых признаков и описание этого процесса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Новые признаки создавать фактически не из чего, данные полностью описывают физические процессы. Во втором пункте, при работе с PCA , было замечено, что 10 признаков описывают 99% дисперсии. Попробуем обучить еще один лес на признаках отобранных PCA. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rfc_pca = RandomForestClassifier(random_state=RANDOM_STATE,n_jobs=-1)\n",
    "gcv_pca = GridSearchCV(rfc_pca, parameters, n_jobs=-1, cv=skf, verbose=1,scoring='roc_auc')\n",
    "train_x_pca = pca.transform(train_x)[:,:10]\n",
    "gcv_pca.fit(train_x_pca, train_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gcv_pca.best_params_, gcv_pca.best_score_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Параметры оказались теми же, точность упала. Проверим тестовую выборку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_x_pca = pca.transform(test_x)\n",
    "y_pred_pca = gcv.best_estimator_.predict_proba(test_x_pca)\n",
    "roc_auc_score(test_y,y_pred_pca[:,1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На тестовой выборке точность упала очень сильно. Уменьшение количества признаков приводит к уменьшению точности модели."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. Построение кривых валидации и обучения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим кривые."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import learning_curve\n",
    "def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,\n",
    "                        n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):\n",
    "    pyplot.figure()\n",
    "    pyplot.title(title)\n",
    "    if ylim is not None:\n",
    "        pyplot.ylim(*ylim)\n",
    "    pyplot.xlabel(\"Training examples\")\n",
    "    pyplot.ylabel(\"Score\")\n",
    "    \n",
    "    train_sizes, train_scores, test_scores = learning_curve(\n",
    "        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)\n",
    "    \n",
    "    train_scores_mean = np.mean(train_scores, axis=1)\n",
    "    train_scores_std = np.std(train_scores, axis=1)\n",
    "    test_scores_mean = np.mean(test_scores, axis=1)\n",
    "    test_scores_std = np.std(test_scores, axis=1)\n",
    "    pyplot.grid()\n",
    "\n",
    "    pyplot.fill_between(train_sizes, train_scores_mean - train_scores_std,\n",
    "                     train_scores_mean + train_scores_std, alpha=0.1,\n",
    "                     color=\"r\")\n",
    "    pyplot.fill_between(train_sizes, test_scores_mean - test_scores_std,\n",
    "                     test_scores_mean + test_scores_std, alpha=0.1, color=\"g\")\n",
    "    pyplot.plot(train_sizes, train_scores_mean, 'o-', color=\"r\",\n",
    "             label=\"Training score\")\n",
    "    pyplot.plot(train_sizes, test_scores_mean, 'o-', color=\"g\",\n",
    "             label=\"Cross-validation score\")\n",
    "\n",
    "    pyplot.legend(loc=\"best\")\n",
    "    return pyplot\n",
    "\n",
    "\n",
    "\n",
    "estimator = gcv.best_estimator_\n",
    "plot_learning_curve(estimator, 'Кривые валидации и обучения', train_x, train_y, ylim=(0.0, 1.01), cv=skf, n_jobs=-1)\n",
    "\n",
    "\n",
    "pyplot.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Модель очень критична к количеству данных. Наблюдается резкий прирост качества при увеличении обучающих примеров."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 11. Прогноз для тестовой или отложенной выборке"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь будем оценивать качество линейного метода и RandomForest на всей выборке. Построим классификатор на основе средних векторов и Евклидова расстояния и рассчитаем его среднюю точность на всей выборке."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tqdm import tqdm\n",
    "all_roc = []\n",
    "for ind,i in tqdm(enumerate(data_dict)):\n",
    "    e_train_x, e_train_y, e_test_x, e_test_y = prepare_data(data_dict,ind)\n",
    "    e_train_x = np.array(e_train_x)  \n",
    "    e_train_y = np.array(e_train_y) \n",
    "    e_test_x = np.array(e_test_x) \n",
    "    e_test_y = np.array(e_test_y) \n",
    "    target = np.mean(e_train_x[np.where(e_train_y == 1)[0]],axis=0)\n",
    "    target_norm = target / np.linalg.norm(target)\n",
    "    e_test_x_normed = e_test_x/ np.linalg.norm(e_test_x,axis=0)[np.newaxis,:]\n",
    "    predictions = np.sqrt(np.sum((e_test_x_normed - target_norm)**2,axis=1))\n",
    "    all_roc.append(roc_auc_score(e_test_y,predictions))\n",
    "\n",
    "np.mean(all_roc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Линейный подход не сработал результат получается случайный. Пробуем RandomForest c параметрами от кроссвалидации."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_roc_forest = []\n",
    "local_forest = RandomForestClassifier(max_depth=10,max_features=4,min_samples_leaf=3,n_estimators=200,random_state=RANDOM_STATE,n_jobs=-1)\n",
    "\n",
    "for ind,i in tqdm(enumerate(data_dict)):\n",
    "    e_train_x, e_train_y, e_test_x, e_test_y = prepare_data(data_dict,ind)\n",
    "    e_train_x = np.array(e_train_x)  \n",
    "    e_train_y = np.array(e_train_y) \n",
    "    e_test_x = np.array(e_test_x) \n",
    "    e_test_y = np.array(e_test_y) \n",
    "\n",
    "    \n",
    "    local_forest.fit(e_train_x,e_train_y)\n",
    "    predictions = local_forest.predict_proba(e_test_x)[:,1]\n",
    "    all_roc_forest.append(roc_auc_score(e_test_y,predictions))\n",
    "\n",
    "np.mean(all_roc_forest)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "RandomForest справился намного лучше.Сильного переобучения при подборе параметров не произошло, результаты отличаются не сильно."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 12. Выводы "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В итоге мы получили хороший результат. Увидели, что линейное сравнение не работает, это говорит о том, что признаки поведения комбинируются сложнее чем рассчитывалось. Хорошо показал себя RandomForest. Был проработан сценарий верификации когда идет сравнение one-vs-all. С помощью случайного леса достигнута средняя ROC-AUC равная 0.9775. Я считаю это хорошим результатом. В данном случае можно сделать вывод о то том, что данная биометрическая верификация работает, и с ней можно работать. Насколько это будет работать в промышленных условиях вопрос открытый, так как там совершенно другие объемы и точность может значительно просесть."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Возможные улучшения:\n",
    "\n",
    "- увеличение базы для приближения условий к промышленным\n",
    "- применение нейронных сетей\n",
    "- применение концепций обучения metric learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Спасибо за внимание!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
